package main.LanguageResource.English.Dictionary.WordNet;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import main.LanguageResource.DefaultWordConstruct;
import main.LanguageResource.LocalPOSFactory;
import main.LanguageResource.POS_Tagger;
import main.LanguageResource.WordConstruct;
import main.initiate.Configuration;


import net.didion.jwnl.*;
import net.didion.jwnl.data.*;
import net.didion.jwnl.data.list.*;
import net.didion.jwnl.data.relationship.*;
import net.didion.jwnl.dictionary.Dictionary;

public class WordNetInstance<E> implements main.LanguageResource.Dictionary<E>, POS_Tagger<E>{

    // Dictionary object
    private static Dictionary dictionary = null;
    private static WordNetInstance singleton = null;

    // Initialize the database!
    private WordNetInstance() {
        
        //String propsFile = "file_properties.xml";
        try {
            JWNL.initialize(new FileInputStream(Configuration.getSingleton().getWordNetPropertyFile()));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (JWNLException e) {
            e.printStackTrace();
        }
        // Create dictionary object
        dictionary = Dictionary.getInstance();
    }
    
    public static WordNetInstance getSingleton() {
        if(singleton == null)
            singleton = new WordNetInstance();
        return singleton;
    }


    // Return array of POS objects for a given String
    public static POS[] getPOS(String s) throws JWNLException {
        // Look up all IndexWords (an IndexWord can only be one POS)
        IndexWordSet set = dictionary.lookupAllIndexWords(s);
        // Turn it into an array of IndexWords
        IndexWord[] words = set.getIndexWordArray();
        // Make the array of POS
        POS[] pos = new POS[words.length];
        for (int i = 0; i < words.length; i++) {
            pos[i] = words[i].getPOS();
        }
        return pos;
    }

    // Just gets the related words for first sense of a word
    // Revised to get the list of related words for the 1st Synset that has them
    // We might want to try all of them
    public static List<Synset> getRelated(IndexWord word, PointerType type) throws JWNLException {
        try {
            Synset[] senses = word.getSenses();
            // Look for the related words for all Senses
            for (int i = 0; i < senses.length; i++) {
                List<Synset> a = getRelated(senses[i],type);
                // If we find some, return them
                if (a != null && !a.isEmpty()) {
                    return a;
                }
            }
        } catch (NullPointerException e) {
            // System.out.println("Oops, NULL problem: " + e);
        }
        return null;
    }

    // Related words for a given sense (do synonyms by default)
    // Probably should implement all PointerTypes
    public static List<Synset> getRelated (Synset sense, PointerType type) throws JWNLException, NullPointerException {
        PointerTargetNodeList relatedList;
        // Call a different function based on what type of relationship you are looking for
        if (type == PointerType.HYPERNYM) {
            relatedList = PointerUtils.getInstance().getDirectHypernyms(sense);
        } else if (type == PointerType.HYPONYM){
            relatedList = PointerUtils.getInstance().getDirectHyponyms(sense);
        } else {
            relatedList = PointerUtils.getInstance().getSynonyms(sense);
        }
        // Iterate through the related list and make an ArrayList of Synsets to send back
        Iterator i = relatedList.iterator();
        List<Synset> a = new ArrayList<Synset>();
        while (i.hasNext()) {
            PointerTargetNode related = (PointerTargetNode) i.next();
            Synset s = related.getSynset();
            a.add(s);
        }
        return a;
    }

    // Just shows the Tree of related words for first sense
    // We may someday want to the Tree for all senses
    public static void showRelatedTree(IndexWord word, int depth, PointerType type) throws JWNLException {
        showRelatedTree(word.getSense(1), depth, type);
    }

    public static void showRelatedTree(Synset sense, int depth, PointerType type) throws JWNLException {
        PointerTargetTree relatedTree;
        // Call a different function based on what type of relationship you are looking for
        if (type == PointerType.HYPERNYM) {
            relatedTree = PointerUtils.getInstance().getHypernymTree(sense,depth);
        } else if (type == PointerType.HYPONYM){
            relatedTree = PointerUtils.getInstance().getHyponymTree(sense,depth);
        } else {
            relatedTree = PointerUtils.getInstance().getSynonymTree(sense,depth);
        }
        // If we really need this info, we wil have to write some code to Process the tree
        // Not just display it  
        relatedTree.print();
    }

    // This method looks for any possible relationship
    public static Relationship getRelationship (IndexWord start, IndexWord end, PointerType type) throws JWNLException {
        // All the start senses
        Synset[] startSenses = start.getSenses();
        // All the end senses
        Synset[] endSenses = end.getSenses();
        // Check all against each other to find a relationship
        for (int i = 0; i < startSenses.length; i++) {
            for (int j = 0; j < endSenses.length; j++) {
                RelationshipList list = RelationshipFinder.getInstance().findRelationships(startSenses[i], endSenses[j], type);
                if (!list.isEmpty())  {
                    return (Relationship) list.get(0);
                }
            }
        }
        return null;
    }

    // If you have a relationship, this function will create an ArrayList of Synsets
    // that make up that relationship
    public static ArrayList getRelationshipSenses (Relationship rel) throws JWNLException {
        ArrayList a = new ArrayList();
        PointerTargetNodeList nodelist = rel.getNodeList();
        Iterator i = nodelist.iterator();
        while (i.hasNext()) {
            PointerTargetNode related = (PointerTargetNode) i.next();
            a.add(related.getSynset());
        }
        return a;
    }

    // Get the IndexWord object for a String and POS
    public static IndexWord getWord(POS pos, String s) throws JWNLException {
        IndexWord word = dictionary.getIndexWord(pos,s);
        return word;
    }
    
    
    public Set<String> getSynonym(String word) throws JWNLException {
        IndexWordSet idxWordSet = null;
        try {
            idxWordSet = Dictionary.getInstance().lookupAllIndexWords(word);
        } catch (JWNLException ex) {
            Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        if(idxWordSet == null)
            return null;
        
        return getSynonym(idxWordSet);
    }
    
    public Set<String> getSynonym(String word, POS pos) throws JWNLException {
        IndexWord w = getWord(pos, word);
        if(w==null)
            return null;
        return getSynonym(w);
    }
    
    public Set<String> getSynonym(IndexWordSet wSet) throws JWNLException {
        if(wSet==null)
            return null;
        Set<String> syns = new HashSet<String>();
        for(IndexWord w:wSet.getIndexWordArray()) {
            Set<String> synonym = getSynonym(w, null);
            if(synonym == null)
                continue;
            syns.addAll(synonym);
        }
        return syns;
    }
    
    
    public Set<String> getSynonym(IndexWordSet wSet, PointerType p) throws JWNLException {
        if(wSet==null)
            return null;
        Set<String> syns = new HashSet<String>();
        for(IndexWord w:wSet.getIndexWordArray()) {
            Set<String> synonym = getSynonym(w, p);
            if(synonym == null)
                continue;
            syns.addAll(synonym);
        }
        return syns;
    }    
    
    
    public Set<String> getSynonym(IndexWord w) throws JWNLException {
        return getSynonym(w, null);
    }
    
    
    public Set<String> getSynonym(IndexWord w, PointerType p) throws JWNLException {
        // Use the helper class to get an ArrayList of similar Synsets for an IndexWord
        if(p==null)
            p = PointerType.SIMILAR_TO;
        List<Synset> a = WordNetInstance.getRelated(w, p); 
        // As long as we have a non-empty ArrayList
        if (a != null && !a.isEmpty()) {
            Set<String> syns = new LinkedHashSet<String>();
            for(Synset s:a) {
                Word[] words = s.getWords();
                if(words == null)
                    continue;
                for(Word word:words) {
                    String lemma = word.getLemma();
                    if(lemma == null)
                        continue;
                    syns.add(lemma);
                }
            }
            return syns;
        }
        return null;
    }
    
    /***************************************************************************
     * Interface methods
     **************************************************************************/

    @Override
    public String getSense(String token) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Set<String> getSynonyms(String token) {
        try {
            return getSynonym(token);
        } catch (JWNLException ex) {
            Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
    
    @Override
    public Set<String> getSynonyms(String word, E tagType) {
        
        POS pos = translateE2POS(tagType);
        Set<String> syns = null;
        if(pos==null) {
            try {
                syns = getSynonym(word);
            } catch (JWNLException ex) {
                Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        else{
            try {
                syns = getSynonym(word, pos);
            } catch (JWNLException ex) {
                Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return syns;
    }
    
    
    public POS translateE2POS(E POSTag) {
        if(POSTag==null)
            return null;
        List<POS> allPOS = POS.getAllPOS();
        for (Iterator<POS> it = allPOS.iterator(); it.hasNext();) {
            POS pos = it.next();
            if(pos.getLabel() == null ? POSTag.toString() == null : pos.getLabel().equals(POSTag.toString()))
                return pos;
        }
        return null;
    }
    

    @Override
    public String getLemma(String token) {
        IndexWordSet idxWordSet = null;
        try {
            idxWordSet = Dictionary.getInstance().lookupAllIndexWords(token);
        } catch (JWNLException ex) {
            Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        if(idxWordSet == null)
            return null;
        return idxWordSet.getLemma();
    }


    @Override
    public String[] tokenize(String phrase) {
        if(phrase == null)
            return null;
        POS[] pos = null;
        try {
            pos = getPOS(phrase);            
        } catch (JWNLException ex) {
            Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        if(pos == null)
            return null;
        String[] tokens = new String[pos.length];
        for(int i=0; i<pos.length; i++) {
            tokens[i] = pos[i].getKey();
        }
        return tokens;
    }

    @Override
    public List<WordConstruct<E>> tagPOS(LocalPOSFactory factory, String phrase) {
        if(phrase == null)
            return null;
        POS[] pos = null;
        try {
            pos = getPOS(phrase);            
        } catch (JWNLException ex) {
            Logger.getLogger(WordNetInstance.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        if(pos == null)
            return null;
        List<WordConstruct<E>> wcList = new ArrayList<WordConstruct<E>>();
        for(int i=0; i<pos.length; i++) {
            WordConstruct wc = new DefaultWordConstruct(factory, pos[i].getKey());
            wc.setPOS(pos[i].getLabel());
            wcList.add(wc);
            wc.getPOS();
        }
        return wcList;
    }
}
